#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <string>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

using namespace std;
const int basesize = 1024;
const int argvnum = 64; // 默认参数最多有64个
const int envnum = 64;

// 全局变量，表示命令的参数个数与参数指针数组
char *gargv[argvnum];
int gargc = 0;

int lastcode = 0; // 记录上一个进程的退出数值

// 我系统的环境变量
char *genv[basesize];
// 全局的环境变量表，如果没修改该进程的环境变量会与父进程共享
// 但是如果进行了cd等操作要修改环境变量表，就会进行深拷贝，不再遇父进程共享

// 维护
char pwd[basesize];        // 方便修改pwd
char pwdenv[basesize * 2]; // 在前面加上PWD=，方便直接修改为环境变量

string GetUserName()
{
    string name = getenv("USER");
    return name.empty() ? "None" : name;
}

string GetHostName()
{
    char hst[basesize] = {"\0"}; // 在ubuntu部分版本下HOTSNAME
    // 不是环境变量而是本地变量
    gethostname(hst, basesize);
    string hostname{hst};
    return hostname.empty() ? "None" : hostname;
}

string GetPwd()
{
    if (nullptr == getcwd(pwd, basesize))
    {
        return "None";
    }
    // 但我们需要手动更新环境变量
    snprintf(pwdenv, sizeof(pwdenv), "PWD=%s", pwd);
    putenv(pwdenv);
    return pwd;
    // string pwd = getenv("PWD");//不能从系统的环境变量找，因为我们的工作路径可能会变化需要手动维护
    // return pwd.empty() ? "None" : pwd;
}

string MakeCommandLine()
{
    // htw@htw250056:~/daily/myshell$
    char CommandLine[basesize];
    string s = GetPwd();
    char str[basesize];
    strncpy(str, s.c_str(), basesize - 1);
    str[basesize - 1] = '\0';
    char *token = strrchr(str, '/');

    snprintf(CommandLine, basesize, "[%s@%s %s]#",
             GetUserName().c_str(), GetHostName().c_str(), token);
    return CommandLine;
}

void PrintCommandLine()
{
    printf("%s", MakeCommandLine().c_str());
    fflush(stdout);
}

bool GetCommandLine(char *command_buffer, int size)
{
    // 用户输入的命令行是一个完整的字符串（可能包含空格)
    //  ls -a -l -n
    char *result = fgets(command_buffer, size, stdin); // stdin输入流中获得数据
    if (!result)                                       // 如果获取失败为空，就返回错误
    {
        return false;
    }

    command_buffer[strlen(command_buffer) - 1] = '\0';
    if (strlen(command_buffer) == 0)
    {
        return false;
    }
    return true;
}

void ParseCommandLine(char *coommand_buffer, int size) // 解析字符串，以空格来拆分
{
    memset(gargv, 0, sizeof(gargv));
    gargc = 0;
    // ls -a -b -c -d
    // 由此我们联想到了当时int main(int argc,char*argv[])
    // 这里我们可以通过strtok来根据空格进行切割
    // char *strtok(char *str, const char *delim);

    const char *sep = " ";
    gargv[gargc++] = strtok(coommand_buffer, sep); // 第一次切割返回

    while (gargv[gargc++] = strtok(nullptr, sep))
        ; // 直到strtok返回nullptr赋给gargv结束while循环
    gargc--;
}

bool ExecuteCommand() // 执行命令
{
    // 由于执行的程序有崩溃风险，所以我们应该创建子进程来执行
    pid_t id = fork(); // 创建子进程
    if (id < 0)
    {
        return false;
    }

    if (id == 0) // 子进程
    {
        // 1.执行命令
        execvpe(gargv[0], gargv, genv);
        // 退出
        exit(1);
    }

    int status = 0;
    pid_t rid = waitpid(id, &status, 0); // 等待子进程
    if (rid > 0)
    {
        if (WIFEXITED(status))
        {
            lastcode = WEXITSTATUS(status);
        }
        else
        {
            lastcode = 100;
        }
        return true;
    }
    return false;
}

void AddEnv(const char *item) // 一个接口，专门添加环境变量
{
    int index = 0;
    while (genv[index])
    {
        index++;
    }
    genv[index++] = (char *)malloc(strlen(item) + 1); // 此时genv[index]没有在栈区开辟内存，所以需要开辟
    strncpy(genv[index], item, strlen(item) + 1);
    genv[index] = nullptr;
}

bool CheckAndExecBuiltCommand() // 判断是否为内建命令
{
    if (strcmp(gargv[0], "cd") == 0)
    {
        // cd会更改pwd
        if (gargc == 2)
        {
            chdir(gargv[1]); // 切换路径
        }
        else
        {
            lastcode = 1;
        }
        return true;
    }
    else if (strcmp(gargv[0], "export") == 0)
    {
        // export也是内建命令
        if (gargc == 2)
        {
            AddEnv(gargv[1]);
        }
        else
        {
            lastcode = 2;
        }
        return true;
    }
    else if (strcmp(gargv[0], "env") == 0)
    {
        for (int i = 0; genv[i]; i++)
        {
            printf("%s\n", genv[i]);
        }

        lastcode = 0;
        return true;
    }
    else if (strcmp(gargv[0], "echo") == 0)
    {
        if (gargc == 2)
        {
            if (gargv[1][0] == '$')
            {
                if (gargv[1][1] == '?')
                {
                    printf("%d\n", lastcode);
                    lastcode = 0;
                }
            }
            else
            {
                int index = 0;
                while (gargv[1][index] == '"')
                {
                    index++;
                }

                for (int i = index; i < strlen(gargv[1]) - 1; ++i)
                {
                    printf("%c", gargv[1][i]);
                }
                printf("\n");
                lastcode = 0;
            }
            return true;
        }
        else
        {
            lastcode = 3;
        }
    }
    return false;
}

void InitEnv() // 启动系统时从父shell中初始化环境变量
{
    extern char **environ; //**获取环境变量

    int index = 0;
    while (environ[index])
    {
        genv[index] = (char *)malloc(strlen(environ[index] + 1)); // genv是myshell的环境变量表

        strncpy(genv[index], environ[index], strlen(environ[index] + 1));
        index++;
    }
    genv[index] = nullptr; // 把系统的环境变量表拷贝到了myshell中
}

int main()
{
    char command_buffer[basesize];
    InitEnv();
    while (true)
    {
        PrintCommandLine(); // 打印命令行提示符
        // 所以要获取环境变量来打印用户名，路径

        if (!GetCommandLine(command_buffer, basesize)) // 获取用户命令
        {
            continue; // 获取失败就重新打印命令行执行获取操作
        }

        // printf("%s", command_buffer);

        ParseCommandLine(command_buffer, strlen(command_buffer)); // 分析命令

        // 但是对于某些命令，比如cd，这种命令会改变环境变量，所以不能由子进程执行
        // 这种内建命令（build command)只能由父进程执行,所以我们要先判断是否是内建命令

        if (CheckAndExecBuiltCommand())
        {
            continue;
        }
        ExecuteCommand(); // 创建子进程执行命令
    }
    return 0;
}
